package com.mm.common.utils.cacheUtil;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @BelongsProject: 3.mindMap_ai
 * @BelongsPackage: com.unfbx.utils
 * @Author : 游在海里的鱼
 * @CreateTime : 2024-05-01  14:36
 * @Version : 1.0
 * @ClassName : TODO
 * @Description : 缓存客户端
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheClient {
    private static final Long CACHE_NULL_TLL_MINUTE = 2L;
    //热点数据的过期时间,6个小时
    private static final Long HOTSPOT_TLL_MINUTE = 360L;
    private static final Long CACHE_EXPIRE_RANDOM = 5L;
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    private static final String LOCK_REFRESH_DATA_PRE = "lock:refresh:";


    private class RedisData{
        Object data;
        LocalDateTime expireTime;
    }


    private final StringRedisTemplate stringRedisTemplate;



    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 设置缓存,有过期时间
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    private void set(String key, Object value,Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value),time,unit);
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 设置缓存,解决[缓存雪崩],设置[过期时间添加随机值]
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    public void setCacheAvalanche(String key,Object value,Long time, TimeUnit unit){
        set(key,value,time+RandomUtil.randomLong(CACHE_EXPIRE_RANDOM),unit);
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 设置热点缓存,解决[缓存击穿],设置[逻辑过期]的键值对
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    public void setLogicalExpire(String key, Object value){
        RedisData redisData = new RedisData();
        redisData.data = value;
        redisData.expireTime = LocalDateTime.now().plusSeconds(TimeUnit.MINUTES.toSeconds(HOTSPOT_TLL_MINUTE));

        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 查询热点数据,判断数据是否[逻辑过期]
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    public <R,ID> R queryLogicalExpire(String keyPre, ID id, Class<R> retType, Function<ID,R>dbFallBack){
        String key = keyPre + id;
        String value = stringRedisTemplate.opsForValue().get(key);
        //在redis中,value并不存在
        if(StrUtil.isBlank(value)){
            return null;
        }
        //在redis中,value存在
        RedisData redisData = JSONUtil.toBean(value,RedisData.class);
        R ret = (R) redisData.data;
        //数据没有过期,直接返回
        if(redisData.expireTime.isAfter(LocalDateTime.now())){
            return ret;
        }
        //数据过期
        String lockKey =LOCK_REFRESH_DATA_PRE + id;
        //获取锁成功,进行数据的覆盖
        if(tryLock(lockKey)){
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    R newData = dbFallBack.apply(id);
                    setLogicalExpire(key,newData);
                } finally {
                    unlock(lockKey);
                }
            });
        }
        //获取锁失败和成功,都返回过期数据
        return ret;
    }

//    /** ---------------------------------------------------------------------------------------------------------------------
//     * @Method  : 降序查询有序集合
//     * @Describe: 设置Zset做降序排序，设置hash来存放具体数据
//     **/
//    //无参数
//    public <R> List<R> queryDescZset(
//            String ZsetPre, String HashPre
//            , Integer start, Integer end, Class<R> retType
//            , Long time, TimeUnit unit //key的有效时间
//            , Supplier<List<R>> dbFallBack//往Zset里添加元素的方法
//            , Function<R,Double> scoring//计算分数
//            , Function<R,String> getId){//获得每个元素的主键
//
//        Set<String> values = stringRedisTemplate.opsForZSet().reverseRange(ZsetPre+"Zset", start, end);
//        List<R> ret = new ArrayList<>();
//        //redis中,value 存在且不为
//        if(values != null && !values.isEmpty()){
//            List<Object> objects = stringRedisTemplate.opsForHash().multiGet(HashPre + "Hash", Arrays.asList(values.toArray()));
//            for(Object object : objects){
//                ret.add(JSONUtil.toBean(object.toString(),retType));
//            }
//            return ret;
//        }
//
//        //redis中,value为null,则在数据库中查找,dbFallBack是调用者提供的查找数据库的方法
//        List<R> dbSet = dbFallBack.get();
//        //数据库中也不存在
//        if(dbSet == null){
//            stringRedisTemplate.opsForZSet().add(ZsetPre+"Zset","",0);
//            stringRedisTemplate.expire(ZsetPre+"Zset", CACHE_NULL_TLL_MINUTE, TimeUnit.MINUTES);
//            return null;
//        }
//        //数据库中存在
//        Map<String,String> hash = new HashMap<>();
//        for(R value : dbSet){
//            String id = getId.apply(value);
//            stringRedisTemplate.opsForZSet().add(ZsetPre+"Zset",id,scoring.apply(value));
//            hash.put(id,JSONUtil.toJsonStr(value));
//        }
//        stringRedisTemplate.opsForHash().putAll(HashPre+"Hash",hash);
//        stringRedisTemplate.expire(ZsetPre+"Zset", time, unit);
//        stringRedisTemplate.expire(HashPre+"Hash", time, unit);
//
//        //返回集合
//        values = stringRedisTemplate.opsForZSet().reverseRange(ZsetPre + "Zset", start, end);
//        List<Object> objects = stringRedisTemplate.opsForHash().multiGet(HashPre + "Hash", Arrays.asList(values.toArray()));
//        for(Object object : objects){
//            ret.add(JSONUtil.toBean(object.toString(),retType));
//        }
//        return ret;
//    }
//    //一个参数
//    public <R,ID> List<R> queryDescZset(
//            String ZsetPre, String HashPre//存放list的Hash前缀，记录排序的Zset前缀
//            , Integer start, Integer end, Class<R> retType//查询的起始，结束，返回类型
//            , Long time, TimeUnit unit //key的有效时间
//            , ID page, Function<ID,List<R>> dbFallBack//往Zset里添加元素的方法
//            , Function<R,Double> scoring//计算分数
//            , Function<R,String> getId){//获得每个元素的主键
//
//        Set<String> values = stringRedisTemplate.opsForZSet().reverseRange(ZsetPre+"Zset", start, end);
//        List<R> ret = new ArrayList<>();
//        //redis中,value 存在且不为
//        if(values != null && !values.isEmpty()){
//            List<Object> objects = stringRedisTemplate.opsForHash().multiGet(HashPre + "Hash", Arrays.asList(values.toArray()));
//            for(Object object : objects){
//                ret.add(JSONUtil.toBean(object.toString(),retType));
//            }
//            return ret;
//        }
//
//        //redis中,value为null,则在数据库中查找,dbFallBack是调用者提供的查找数据库的方法
//        List<R> dbSet = dbFallBack.apply(page);
//        //数据库中也不存在
//        if(dbSet == null){
//            stringRedisTemplate.opsForZSet().add(ZsetPre+"Zset","",0);
//            stringRedisTemplate.expire(ZsetPre+"Zset", CACHE_NULL_TLL_MINUTE, TimeUnit.MINUTES);
//            return null;
//        }
//        //数据库中存在
//        Map<String,String> hash = new HashMap<>();
//        for(R value : dbSet){
//            String id = getId.apply(value);
//            stringRedisTemplate.opsForZSet().add(ZsetPre+"Zset",id,scoring.apply(value));
//            hash.put(id,JSONUtil.toJsonStr(value));
//        }
//        stringRedisTemplate.opsForHash().putAll(HashPre+"Hash",hash);
//        stringRedisTemplate.expire(ZsetPre+"Zset", time, unit);
//        stringRedisTemplate.expire(HashPre+"Hash", time, unit);
//
//        //返回集合
//        values = stringRedisTemplate.opsForZSet().reverseRange(ZsetPre + "Zset", start, end);
//        List<Object> objects = stringRedisTemplate.opsForHash().multiGet(HashPre + "Hash", Arrays.asList(values.toArray()));
//        for(Object object : objects){
//            ret.add(JSONUtil.toBean(object.toString(),retType));
//        }
//
//        return ret;
//    }
//    //两个参数
//    public <R,ID1,ID2> List<R> queryDescZset(
//              String ZsetPre, String HashPre//存放list的Hash前缀，记录排序的Zset前缀
//            , Integer start, Integer end, Class<R> retType//查询的起始，结束，返回类型
//            , Long time, TimeUnit unit //key的有效时间
//            , ID1 uid, ID2 page, BiFunction<ID1,ID2,List<R>> dbFallBack//往Zset里添加元素的方法
//            , Function<R,Double> scoring//计算分数
//            , Function<R,String> getId){//获得每个元素的主键
//
//        Set<String> values = stringRedisTemplate.opsForZSet().reverseRange(ZsetPre+"Zset", start, end);
//        List<R> ret = new ArrayList<>();
//        //redis中,value 存在且不为
//        if(values != null && !values.isEmpty()){
//            for(String value : values){
//                ret.add(JSONUtil.toBean((String) stringRedisTemplate.opsForHash().get(HashPre+"Hash",value),retType));
//            }
//            return ret;
//        }
//
//        //redis中,value为null,则在数据库中查找,dbFallBack是调用者提供的查找数据库的方法
//        List<R> dbSet = dbFallBack.apply(uid,page);
//        //数据库中也不存在
//        if(dbSet == null){
//            stringRedisTemplate.opsForZSet().add(ZsetPre+"Zset","",0);
//            stringRedisTemplate.expire(ZsetPre+"Zset", CACHE_NULL_TLL_MINUTE, TimeUnit.MINUTES);
//            return null;
//        }
//        //数据库中存在
//        Map<String,String> hash = new HashMap<>();
//        for(R value : dbSet){
//            String id = getId.apply(value);
//            stringRedisTemplate.opsForZSet().add(ZsetPre+"Zset",id,scoring.apply(value));
//            hash.put(id,JSONUtil.toJsonStr(value));
//        }
//        stringRedisTemplate.opsForHash().putAll(HashPre+"Hash",hash);
//        stringRedisTemplate.expire(ZsetPre+"Zset", time, unit);
//        stringRedisTemplate.expire(HashPre+"Hash", time, unit);
//
//        //返回集合
//        values = stringRedisTemplate.opsForZSet().reverseRange(ZsetPre + "Zset", start, end);
//        List<Object> objects = stringRedisTemplate.opsForHash().multiGet(HashPre + "Hash", Arrays.asList(values.toArray()));
//        for(Object object : objects){
//            ret.add(JSONUtil.toBean(object.toString(),retType));
//        }
//
//        return ret;
//    }



//    /** ---------------------------------------------------------------------------------------------------------------------
//     * @Method  : 修改和新增排名元素
//     * @Describe: TODO
//     **/
//    public <R> Boolean updateZset(String ZsetPre,String HashPre,String id,R value,Double score){
//        stringRedisTemplate.opsForHash().put(HashPre + "Hash", id, JSONUtil.toJsonStr(value));
//        return stringRedisTemplate.opsForZSet().add(ZsetPre + "Zset", id, score);
//    }
//
//
//    /** ---------------------------------------------------------------------------------------------------------------------
//     * @Method  : 清除排名元素
//     * @Describe: TODO
//     **/
//     public Long removeZset(String ZsetPre,String HashPre,String id){
//         stringRedisTemplate.opsForHash().delete(HashPre + "Hash", id);
//         Long remove = stringRedisTemplate.opsForZSet().remove(ZsetPre + "Zset", id);
//         return remove;
//     }


    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 查询数据,解决[缓存穿透],设置[空结果缓存]的键值对
     * @Describe: 设置list
     **/
    public <R,ID> List<R> queryListPassThrough(String keyPre, ID id, Class<R> retType,
                                               Long time, TimeUnit unit, Function<ID,List<R>> dbFallBack){
        String key = keyPre + id;
        String value = stringRedisTemplate.opsForValue().get(key);
        //redis中,value 存在且不为空字符串
        if(StrUtil.isNotBlank(value)){
            return JSONUtil.toList(value,retType);
        }
        //redis中,value不为null且为空字符串
        if(value != null){
            return null;
        }
        //redis中,value为null,则在数据库中查找,dbFallBack是调用者提供的查找数据库的方法
        List<R> ret = dbFallBack.apply(id);
        //数据库中也不存在
        if(ret == null || ret.isEmpty()){
            set(key,"",CACHE_NULL_TLL_MINUTE,TimeUnit.MINUTES);
            return null;
        }
        //数据库中存在
        setCacheAvalanche(key,JSONUtil.toJsonStr(ret),time,unit);
        return ret;
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 查询数据,解决[缓存穿透],设置[空结果缓存]的键值对
     * @Describe: TODO
     * @modifier: [public] [实例方法]
    **/
    public <R,ID> R queryPassThrough(String keyPre, ID id, Class<R> retType,
                                   Long time, TimeUnit unit, Function<ID,R> dbFallBack){
        String value = stringRedisTemplate.opsForValue().get(keyPre);
        //redis中,value 存在且不为空字符串
        if(StrUtil.isNotBlank(value)){
            return JSONUtil.toBean(value,retType);
        }
        //redis中,value不为null且为空字符串
        if(value != null){
            return null;
        }
        //redis中,value为null,则在数据库中查找,dbFallBack是调用者提供的查找数据库的方法
        R ret = dbFallBack.apply(id);
        //数据库中也不存在
        if(ret == null){
            set(keyPre,"",CACHE_NULL_TLL_MINUTE,TimeUnit.MINUTES);
            return null;
        }
        //数据库中存在
        setCacheAvalanche(keyPre,JSONUtil.toJsonStr(ret),time,unit);
        return ret;
    }

//    /** ---------------------------------------------------------------------------------------------------------------------
//     * @Method  : 修改
//     * @Describe: TODO
//     **/
//     public <R> void updateCache(String key,R value){
//        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value));
//    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 删除
     **/
    public boolean removeCache(String key){
        return stringRedisTemplate.delete(key);
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 删除多个
     **/
    public void removeCaches(String pattern){
        Set<String> keys = stringRedisTemplate.keys(pattern);
        if (keys != null && !keys.isEmpty()) {
            stringRedisTemplate.delete(keys);
        }
    }


    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取锁,redis中的锁
     * @Describe: TODO
     * @modifier: [private] [实例方法]
    **/
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //当flag为null时也能返回false
        return BooleanUtil.isTrue(flag);
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 释放锁,redis中的锁
     * @Describe: TODO
     * @modifier: [private] [实例方法]
    **/
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }



}
